using System;
using System.Collections.Generic;

namespace SeaVM
{
    public class VmTypeError : System.Exception
    {
        public string msg;
        public VmTypeError(string msg="")
        {
            this.msg = msg;
        }
    }

    public class VmRunError : System.Exception
    {
        public string msg;
        public VmRunError(string msg="")
        {
            this.msg = msg;
        }
    }

    public enum VMStatus
    {
        Stop,
        Error,
        Runing,
        RunEnd,
    }

    public class VM
    {
        public VMStatus status = VMStatus.Stop;
        public Memory memory;
        public Stack<Data> stack = new Stack<Data>();
        public Dictionary<System.String,int> defines = new Dictionary<string, int>();
        public Dictionary<System.String,int> jump_nodes = new Dictionary<string, int>();
        public List<Instruction> instructions = new List<Instruction>();
        public int line = 0;

        public VM(int memory_size = 256)
        {
            memory = new Memory(memory_size);
        }

        public void PushInstruction(Instruction inst)
        {
            instructions.Add(inst);
        }
        public void PushJumpNode(string name)
        {
            jump_nodes[name] = instructions.Count;
        }

        public void RunInstruction(Instruction inst)
        {
            switch(inst.opcode)
            {
                case Opcode.Push:{Instructions.Push.Run(this,inst);}break;
                case Opcode.Pop:{Instructions.Pop.Run(this,inst);}break;
                case Opcode.Add:{Instructions.Add.Run(this,inst);}break;
                case Opcode.Sub:{Instructions.Sub.Run(this,inst);}break;
                case Opcode.Mul:{Instructions.Mul.Run(this,inst);}break;
                case Opcode.Div:{Instructions.Div.Run(this,inst);}break;
                case Opcode.Mod:{Instructions.Mod.Run(this,inst);}break;
                case Opcode.Gr:{Instructions.Gr.Run(this,inst);}break;
                case Opcode.Le:{Instructions.Le.Run(this,inst);}break;
                case Opcode.Eq:{Instructions.Eq.Run(this,inst);}break;
                case Opcode.Neq:{Instructions.Neq.Run(this,inst);}break;
                case Opcode.Jmp:{Instructions.Jmp.Run(this,inst);}break;
                case Opcode.Cjmp:{Instructions.Cjmp.Run(this,inst);}break;
                case Opcode.Define:{Instructions.Define.Run(this,inst);}break;
                case Opcode.Print:{Instructions.Print.Run(this,inst);}break;
                default:{
                    line++;
                    break;
                }
            }
        }

        public void Run()
        {
            status = VMStatus.Runing;
            while(true)
            {
                if(line < 0 || line >= instructions.Count)
                {
                    status = VMStatus.RunEnd;
                    break;
                }
                try
                {
                    Instruction inst = instructions[line];
                    RunInstruction(inst);
                }
                catch(VmRunError err)
                {
                    Console.WriteLine("Run Error!:"+line+":"+err.msg);
                    break;
                }
                catch(VmTypeError err)
                {
                    Console.WriteLine("Type Error!:"+line+":"+err.msg);
                    break;
                }
            }
        }
    }
}